// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;

namespace SnapObjects.Data
{
    internal class ModelSqlHelper
    {
        protected readonly PocoModelMeta _modelMeta;
        protected readonly IReadOnlyDictionary<int, string> _tableAlias;

        public ModelSqlHelper(Type modelType)
        {
            _modelMeta = ModelMetaManager.GetModelMeta(modelType);

            _tableAlias = this.BuildTableAlias();
        }

        public PocoModelMeta ModelMeta => _modelMeta;

        public IReadOnlyDictionary<int, string> TableAliases => _tableAlias;

        private IReadOnlyDictionary<int, string> BuildTableAlias()
        {
            var tableAlias = new Dictionary<int, string>();
            string alias;

            if (_modelMeta.TableDefined || _modelMeta.Tables.Count == 0)
            {
                tableAlias.Add(PocoModelMeta.TABLE_ID, _modelMeta.GetFullTableName(PocoModelMeta.TABLE_ID));
            }

            foreach (var table in _modelMeta.Tables)
            {
                if (table is IFromTable)
                {
                    alias = String.IsNullOrWhiteSpace(((IFromTable)table).Alias) ?
                                        _modelMeta.GetFullTableName(((IFromTable)table).Id) :
                                            ((IFromTable)table).Alias;

                    tableAlias.Add(((IFromTable)table).Id, alias);
                }
                else if (table is IJoinTable)
                {
                    alias = String.IsNullOrWhiteSpace(((IJoinTable)table).Alias) ?
                                        _modelMeta.GetFullTableName(((IJoinTable)table).Id) :
                                            ((IJoinTable)table).Alias;

                    tableAlias.Add(((IJoinTable)table).Id, alias);
                }
            }

            return tableAlias;
        }

        public virtual string BuildPropertyRef(string expression)
        {
            if (!_modelMeta.PropertyMap.TryGetValue(expression, out var propertyMeta))
            {
                throw new InvalidOperationException("Specified property is not exists, property: " + expression);
            }

            //Foreign table
            if (propertyMeta.SqlColumn != null &&
                    propertyMeta.SqlColumn.TableAliasDefined)
            {
                var sqlColumn = propertyMeta.SqlColumn;

                return sqlColumn.TableAlias + "." + sqlColumn.Column;
            }
            else if (propertyMeta.SqlCompute != null)
            {
                var sqlComputer = propertyMeta.SqlCompute;

                return sqlComputer.AliasDefined ? sqlComputer.Alias : sqlComputer.Expression;
            }
            else //Main table
            {
                return this.GetMappedTableAliasOrName() + "." + propertyMeta.MappedColumn;
            }
        }

        public string GetColumnWithAlias(int tableId, string column, bool forceAlias)
        {
            if (_tableAlias.TryGetValue(tableId, out var alias))
            {
                return alias + "." + column;
            }
            else if (forceAlias)
            {
                return _modelMeta.GetFullTableName(tableId) + "." + column;
            }
            else
            {
                return column;
            }
        }

        public string GetMappedTableFullName()
        {
            if (_modelMeta.TableDefined)
            {
                return _modelMeta.GetFullTableName(PocoModelMeta.TABLE_ID);
            }
            else
            {
                return String.IsNullOrWhiteSpace(_modelMeta.MappedSchema) ?
                    _modelMeta.MappedTable : _modelMeta.MappedSchema + "." + _modelMeta.MappedTable;
            }
        }

        public string GetMappedTableAliasOrName()
        {
            if (_modelMeta.TableDefined)
            {
                return _modelMeta.GetFullTableName(PocoModelMeta.TABLE_ID);
            }
            else if (!String.IsNullOrWhiteSpace(_modelMeta.MappedTableAlias))
            {
                return _modelMeta.MappedTableAlias;
            }
            else
            {
                return _modelMeta.MappedTable;
            }
        }

        public string GetTableAliasOrName(int tableId, bool throwIfFail)
        {
            if (_tableAlias.TryGetValue(tableId, out var alias))
            {
                return alias;
            }
            else if (throwIfFail)
            {
                throw new InvalidOperationException("Fail to get table alias, tableId: " + tableId);
            }
            else
            {
                return _modelMeta.GetFullTableName(tableId);
            }
        }

        public void BuildWhereForKeys(
            ISqlFromBuilder sqlFromBuilder,
            IReadOnlyList<IPocoProperty> properties)
        {
            var keyProperties = properties.Where(p => p.IsKey);

            var index = 0;

            foreach (var key in keyProperties)
            {
                var column = key.MappedColumn;

                if (index == 0)
                {
                    sqlFromBuilder.Where(column, SqlBuilder.Parameter(key.DataType, key.Name?.ToLower()));
                }
                else
                {
                    sqlFromBuilder.AsWhere().AndWhere(column, SqlBuilder.Parameter(key.DataType, key.Name?.ToLower()));
                }

                index++;
            }

            if (index == 0)
            {
                throw new InvalidOperationException("No Key(s) defined in model.");
            }
        }
    }
}
